﻿using System;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using Microsoft.Data.Tools.Schema.Sql.UnitTesting;
using Microsoft.Data.Tools.Schema.Sql.UnitTesting.Conditions;

namespace SSDTConditions
{
    [ExportTestCondition("String Value", typeof(StringValueCondition))]
    public class StringValueCondition : TestCondition
    {
        public enum ComparisonType 
        { 
            Equals, 
            Contains, 
            NotContains, 
            StartsWith, 
            EndsWith 
        };

        public enum StringValueVerificationCode 
        {
            Ok,                 // condition is true
            NullExptected,      // get not null when null is expected
            FalseCondition      // condition is false
        };
        
        private int _resultSet;
        private int _column;
        private int _rowNumber;
        private string _expectedValue;
        private bool _nullExpected;
        private ComparisonType _comparison;
        private bool _caseSensitive;
        private int _batch;

        public StringValueCondition()
        {
            _resultSet = 1;
            _column = 1;
            _rowNumber = 1;
            _expectedValue = "";
            _nullExpected = false;
            _comparison = ComparisonType.Equals;
            _caseSensitive = false;
            _batch = 1;
        }

        // method you need to override to perform the condition verification
        public override void Assert(DbConnection validationConnection, SqlExecutionResult[] results)
        {
            // call base for parameter validation
            base.Assert(validationConnection, results);

            // verify batch exists
            if (results.Length < _batch)
            {
                throw new DataException(String.Format("Batch {0} does not exist", _batch));
            }

            SqlExecutionResult result = results[_batch - 1];

            // verify resultset exists
            if (result.DataSet.Tables.Count < ResultSet)
            {
                throw new DataException(String.Format("ResultSet {0} does not exist", ResultSet));
            }

            DataTable table = result.DataSet.Tables[ResultSet - 1];

            // actual condition verification
            StringValueVerificationCode verificationResult = StringConditionVerification(table);

            switch(verificationResult)
            {
                case StringValueVerificationCode.NullExptected:
                    throw new DataException(String.Format(
                        "ResultSet {0} Row {1} Column {2} does not contain Null value expected",
                        ResultSet, RowNumber, Column));
                case StringValueVerificationCode.FalseCondition:
                    throw new DataException(String.Format(
                        "ResultSet {0} Row {1} Column {2} {3} the string '{4}' expected",
                        ResultSet, RowNumber, Column, ComparisonNegativePhrase(Comparison), ExpectedValue));
            }
        }

        public StringValueVerificationCode StringConditionVerification(DataTable table)
        {
            StringValueVerificationCode returnCode = StringValueVerificationCode.Ok;

            if (NullExpected)
            {
                if (table.Rows[RowNumber - 1][Column - 1] != DBNull.Value)
                {
                    returnCode = StringValueVerificationCode.NullExptected;
                }
            }
            else
            {
                bool comparisonResult = true;
                string actual = table.Rows[RowNumber - 1][Column - 1].ToString(),
                    expected = ExpectedValue;
                // case insensitive
                if (CaseSensitive == false)
                {
                    // convert to lower case
                    actual = actual.ToLower();
                    expected = expected.ToLower();
                }

                switch (Comparison)
                {
                    case ComparisonType.Equals:
                        comparisonResult = actual.Equals(ExpectedValue);
                        break;
                    case ComparisonType.Contains:
                        comparisonResult = actual.Contains(ExpectedValue);
                        break;
                    case ComparisonType.NotContains:
                        comparisonResult = !actual.Contains(ExpectedValue);
                        break;
                    case ComparisonType.StartsWith:
                        comparisonResult = actual.StartsWith(ExpectedValue);
                        break;
                    case ComparisonType.EndsWith:
                        comparisonResult = actual.EndsWith(ExpectedValue);
                        break;
                }

                if (!comparisonResult)
                {
                    returnCode = StringValueVerificationCode.FalseCondition;
                }
            }

            return returnCode;
        }

        // this method is called to provide the string shown in the
        // test conditions panel grid describing what the condition tests
        public override string ToString()
        {
            return String.Format(
                "Condition fails if ResultSet {0} Row {1} Column {2} {3} '{4}'",
                ResultSet, RowNumber, Column, ComparisonNegativePhrase(Comparison), ExpectedValue);
        }

        private string ComparisonNegativePhrase(ComparisonType comparison)
        {
            // obtaining phrase for comparison function in negative form
            string comparisonPhrase = "";
            switch (Comparison)
            {
                case ComparisonType.Equals:
                    comparisonPhrase = "is not equal";
                    break;
                case ComparisonType.Contains:
                    comparisonPhrase = "does not contain";
                    break;
                case ComparisonType.NotContains:
                    comparisonPhrase = "contains";
                    break;
                case ComparisonType.StartsWith:
                    comparisonPhrase = "does not start with";
                    break;
                case ComparisonType.EndsWith:
                    comparisonPhrase = "does not end with";
                    break;
            }

            return comparisonPhrase;
        }

        // below are the test condition properties
        // that are exposed to the user in the property browser
        #region Properties

        // property specifying the resultset for which the test condition would be applied
        [Category("Test Condition")]
        [DisplayName("ResultSet")]
        [Description("ResultSet Number")]
        public int ResultSet
        {
            get { return _resultSet; }

            set
            {
                //basic validation
                if (value < 1)
                    throw new ArgumentException("ResultSet cannot be less than 1");

                _resultSet = value;
            }
        }

        // property specifying expected column count
        [Category("Test Condition")]
        [DisplayName("Column")]
        [Description("Column Number")]
        public int Column
        {
            get { return _column; }

            set
            {
                //basic validation
                if (value < 0)
                {
                    throw new ArgumentException("Column number cannot be less than 0");
                }

                _column = value;
            }
        }

        // property specifying expected row number count
        [Category("Test Condition")]
        [DisplayName("RowNumber")]
        [Description("Row Number")]
        public int RowNumber
        {
            get { return _rowNumber; }

            set
            {
                //basic validation
                if (value < 0)
                {
                    throw new ArgumentException("Row number cannot be less than 0");
                }

                _rowNumber = value;
            }
        }

        // property specifying expected string value
        [Category("Test Condition")]
        [DisplayName("ExpectedValue")]
        [Description("Expected Value")]
        public string ExpectedValue
        {
            get { return _expectedValue; }

            set
            {
                //basic validation
                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentException("Expected value cannot be empty");
                }

                _expectedValue = value;
            }
        }

        // property specifying expected row number count
        [Category("Test Condition")]
        [DisplayName("NullExpected")]
        [Description("Null Expected")]
        public bool NullExpected
        {
            get { return _nullExpected; }

            set
            {
                _nullExpected = value;
            }
        }

        // property specifying expected string value
        [Category("Test Condition")]
        [DisplayName("Comparison")]
        [Description("Comparison Function")]
        public ComparisonType Comparison
        {
            get { return _comparison; }

            set
            {
                _comparison = value;
            }
        }

        // property specifying case sensitivity
        [Category("Test condition")]
        [DisplayName("CaseSensitive")]
        [Description("Case sensitive")]
        public bool CaseSensitive
        {
            get { return _caseSensitive; }
            
            set
            {
                _caseSensitive = value;
            }
        }
        #endregion
    }
}
